NOTE
2.2 Redis RateLimiter
A Redis-based rate limiter using a counter algorithm and Lua scripts.
This is a historical learning note and may contain outdated or incomplete understanding.
1. What It Is
- Rate limiting implemented based on Redis.
2. Counter Algorithm
2.1. Interface
2.1.1. Increment Access Count by One
- Parameter: key
- Returns:
- current access count;
- remaining time before the access count resets.
2.1.2. Get the Current Access Count
- Parameter: key
- Returns:
- current access count;
- remaining time before the access count resets.
2.2. Logic
-
Increment access count by one
local freqKey = KEYS[1] local freqTTL = tonumber(ARGV[1]) local function isFreqKeyExists() return redis.call("EXISTS", freqKey) end local function incrFreqCount() return redis.call("INCR", freqKey) end local function getFreqTTL() return redis.call("TTL", freqKey) end local function initFreqKey() return redis.call("SET", freqKey, 1, "EX", freqTTL) end local function main() if isFreqKeyExists() == 1 then local freqCount = incrFreqCount() local freqTTL = getFreqTTL() return {freqCount, freqTTL} else initFreqKey() return {1, freqTTL} end end return main() -
Get the current access count
local freqKey = KEYS[1] local function getFreqCount() return redis.call("GET", freqKey) end local function getFreqTTL() return redis.call("TTL", freqKey) end local function main() local freqCount = getFreqCount() if not freqCount then return {0, 0} else local freqTTL = getFreqTTL() return {freqCount, freqTTL} end end return main()
2.2.1. Why Wrap It in a Lua Script?
- Package multiple commands together to avoid multiple round trips to the server and improve efficiency.
- Of course, pipeline can also implement this.
- Prevent concurrency problems.
- For example, the following scenario would confuse the business side: the access count is clearly 0, so why is the next reset time 60 seconds?
- Get current access count:
getreturns an access count of 0. - Increment access count:
incrmakes the count 1. - Increment access count:
expiresets the timeout to 60 seconds. - Get current access count:
pttlreturns 60 seconds.
-- appid_business-key_current-time-under-the-current-time-type
local freqKey = KEYS[1]
-- Logical expiration timestamp: 1651420800
local freqExpireTs = tonumber(ARGV[1])
-- Actual expiration time: ttl + 7 days: ttl + 604800
local freqTTL = tonumber(ARGV[2])
-- Current timestamp
local currentTimeTs = tonumber(ARGV[3])
local function isFreqKeyExists(freqKey)
local isExists = redis.call("EXISTS", freqKey)
return isExists == 1
end
local function getFreqExpireTs()
return tonumber(redis.call("HGET", freqKey, "expire"))
end
local function incrFreqCount()
return redis.call("HINCRBY", freqKey, "count", 1)
end
local function initFreqKey(freqKey, freqExpireTs, freqTTL)
redis.call("HMSET", freqKey, "count", 1, "expire", freqExpireTs)
redis.call("EXPIRE", freqKey, freqTTL)
end
local function isFreqExpired(freqExpireTs, currentTimeTs)
return currentTimeTs >= freqExpireTs
end
local function main()
if isFreqKeyExists(freqKey) then
local freqExpireTs = getFreqExpireTs(freqKey)
if isFreqExpired(freqExpireTs, currentTimeTs)==false then
local freqCount = incrFreqCount()
return {freqCount, freqExpireTs}
end
end
initFreqKey(freqKey, freqExpireTs, freqTTL)
return {1, freqExpireTs}
end
return main()
local freqKey = KEYS[1]
local currentTimeTs = tonumber(ARGV[1])
local function isFreqKeyExists(freqKey)
local isExists = redis.call("EXISTS", freqKey)
return isExists == 1
end
local function getFreq(freqKey)
local freq = redis.call("HMGET", freqKey, "count", "expire")
return tonumber(freq[1]), tonumber(freq[2])
end
local function isFreqAlived(freqExpireTs, currentTimeTs)
return currentTimeTs < freqExpireTs
end
local function main()
if isFreqKeyExists(freqKey) then
local freqCount, freqExpireTs = getFreq(freqKey)
if isFreqAlived(freqExpireTs, currentTimeTs) then
return {freqCount, freqExpireTs}
end
end
return {0, currentTimeTs}
end
return main()
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub